// Qt Includes
#include <QMetaMethod>
#include <QApplication>
#include <QVariant>
#include <QGenericArgument>
#include <QRegExp>
#include <QStringList>
#include <QMutexLocker>

// Includes
#include "shell.h"
// Defines


// Globals


/**************************************************************************************/

IShell::IShell(QString objName, QObject* obj, bool /*bGuiContext*/) : m_pObj(obj)
    {
    m_pObj->setObjectName(objName);
    RShell::get()->registerObject(m_pObj);
    }

IShell::~IShell()
    {
    RShell::get()->unregisterObject(m_pObj->objectName());
    }

void IShell::setHelp(const QMap<QString, QString>& help)
    {
    m_help = help;
    }
    
QMap<QString, QString> IShell::getHelp() const
    {
    return m_help;
    }

/**************************************************************************************/

IShellObject::IShellObject(QString objName, QObject* obj, bool bGuiContext)
    : QObject(),
      IShell(objName, obj, bGuiContext)
    {
    }

IShellObject::~IShellObject()
    {
    }

/**************************************************************************************/


RShell* RShell::m_pInstance = 0;

RShell* RShell::get()
    {
    if (!m_pInstance)
        {
        m_pInstance = new RShell();
        }
    return m_pInstance;
    }

RShell::RShell() :m_MapLock(QMutex::Recursive)
    {
    m_supportedParamTypes << "int" << "bool" << "short" << "long" << "float" << "double" << "QString" << "unsigned int" << "unsigned short" << "unsigned long" << "uint" << "ushort" << "ulong" << "udouble" << "uchar";
    }

RShell::~RShell()
    {}

void RShell::registerObject(QObject* obj)
    {
//    LOG_NO_ENTRY;

    const QMetaObject* pMetaObj = obj->metaObject();
    if(pMetaObj)
        {
//        LOG_LEVELEX(1, "---------------------------------------------------");
//        LOG_LEVELEX(1, "Objectname <%s>", qPrintable(obj->objectName()));
//        LOG_LEVELEX(1, "Classname <%s>", pMetaObj->className());
//        LOG_LEVELEX(1, "Nr of methods <%d>", pMetaObj->methodCount());
//        int nMethodCnt = pMetaObj->methodCount();
//        for(int i=0; i<nMethodCnt; i++)
//            {
//            QMetaMethod method = pMetaObj->method(i);
//            LOG_LEVELEX(1, "Method: <%s>", method.signature());

//            QList<QByteArray> paramTypes =  method.parameterTypes();
//            for(int i=0; i<paramTypes.size(); i++)
//                {
//                LOG_LEVELEX(5, "ParamType: <%s>", paramTypes[i].data());
//                }

//            }
//        LOG_LEVELEX(1, "---------------------------------------------------");

        QMutexLocker lock(&m_MapLock);
        m_ObjMap.insert(obj->objectName(), obj);

        emit shellObjectsChanged();
        }
    }

void RShell::unregisterObject(QString objName)
    {
    QMutexLocker lock(&m_MapLock);

    if(m_ObjMap.contains(objName))
        {
        m_ObjMap.remove(objName);
        emit shellObjectsChanged();
        }
    }

bool RShell::addDataTypeAlias(QString dataType, QString alias)
    {
//    LOG_NO_ENTRY;

    if(!m_supportedParamTypes.contains(dataType))
        {
//        LOG_WARNING("Datatype <%s> not supported", qPrintable(dataType));
        return false;
        }

    m_aliasMap.insert(alias, dataType);
    return true;
    }

QStringList RShell::getAvailableClasses()
    {
    QStringList classes;
    QMutexLocker lock(&m_MapLock);

    QMap<QString, QObject*>::iterator it;
    for(it=m_ObjMap.begin(); it!=m_ObjMap.end(); it++)
        {
        const QMetaObject* pMetaObj = (*it)->metaObject();
        if(pMetaObj)
            {
            if(!classes.contains(QString(pMetaObj->className())))
                classes << QString(pMetaObj->className());
            }
        }

    return classes;
    }

QStringList RShell::getAvailableObjects(QString className)
    {
    QStringList objects;
    QMutexLocker lock(&m_MapLock);

    QMap<QString, QObject*>::iterator it;
    for(it=m_ObjMap.begin(); it!=m_ObjMap.end(); it++)
        {
        const QMetaObject* pMetaObj = (*it)->metaObject();
        if(pMetaObj)
            {
            if(pMetaObj->className() == className)
                objects << QString((*it)->objectName());
            }
        }

    return objects;
    }

QStringList RShell::getAvailableFunctions(QString className, FunctionAccess a)
    {
    QStringList functions;
    QMutexLocker lock(&m_MapLock);

    QObject qobj;
    const QMetaObject* pQObjMetaObj = qobj.metaObject();

    QMap<QString, QObject*>::iterator it;
    for(it=m_ObjMap.begin(); it!=m_ObjMap.end(); it++)
        {
        const QMetaObject* pMetaObj = (*it)->metaObject();
        if(pMetaObj && pQObjMetaObj)
            {
            if(pMetaObj->className() == className)
                {
                int nMethodCnt = pMetaObj->methodCount();
                for(int i=0; i<nMethodCnt; i++)
                    {
                    bool bUnsupportedMethod = false;

                    // skip all QObject functions from list
                    int nTmp = pQObjMetaObj->methodCount();
                    for(int j=0; j<nTmp; j++)
                        {
                        if(QString(pQObjMetaObj->method(j).signature()) == QString(pMetaObj->method(i).signature()))
                            bUnsupportedMethod = true;

                        if( (a == FAPublicOnly) && (pMetaObj->method(i).access() != QMetaMethod::Public) )
                            bUnsupportedMethod = true;
                        }

                    QList<QByteArray> paramTypes =  pMetaObj->method(i).parameterTypes();
                    for(int x=0; x<paramTypes.size(); x++)
                        {
                        QString paramType = QString(paramTypes[x].data());
                        // remove any const specifiers from type
                        paramType.replace("const ", "");
                        // remove any & specifiers from type
                        paramType.replace("&", "");

                        if( (!m_supportedParamTypes.contains(paramType)) && (m_aliasMap.value(paramType).isEmpty()) )
                            bUnsupportedMethod = true;
                        }

                    // if it is not an QObject Method append to list
                    if(!bUnsupportedMethod)
                        functions << QString(pMetaObj->method(i).signature());
                    }
                break;
                }
            }
        }

    return functions;
    }

QString RShell::getParameterNames(QString className, QString funcName)
    {
    QString paramNames;
    QMutexLocker lock(&m_MapLock);

    QMap<QString, QObject*>::iterator it;
    for(it=m_ObjMap.begin(); it!=m_ObjMap.end(); it++)
        {
        const QMetaObject* pMetaObj = (*it)->metaObject();
        if(pMetaObj)
            {
            if(pMetaObj->className() == className)
                {
                QMap<QString, QString> help;
                IShell *pIShellObj = dynamic_cast<IShell*>(it.value());
                if (pIShellObj)
                    help = pIShellObj->getHelp();

                const int nMethodCnt = pMetaObj->methodCount();
                for(int i=0; i<nMethodCnt; i++)
                    {
                    if (pMetaObj->method(i).signature() == funcName)
                        {
                        const QList<QByteArray> paramTypes = pMetaObj->method(i).parameterTypes();
                        const QStringList tmp = funcName.split("(");
                        const QString theFuncName = (tmp.size() > 0) ? tmp.first() : funcName;
                        const QString synopsis = help.value(theFuncName + "#synopsis", "");
                        const QString preCondition = help.value(theFuncName + "#preCondition", "");
                        const QString postCondition = help.value(theFuncName + "#postCondition", "");

                        paramNames = "";
                        if (synopsis.size() > 0)
                            paramNames = "<b>Synopsis:</b> " + synopsis + "<br><br>";
                        if(preCondition.size() > 0)
                            paramNames +="<b>Precondition: </b>" + preCondition + "<br><br>";
                        if(postCondition.size() > 0)
                            paramNames +="<b>Postcondition: </b>" + postCondition + "<br><br>";
                        if (paramTypes.size() > 0)
                            paramNames += "<b>Parameter:</b><br>";

                        for(int x=0; x<paramTypes.size(); x++)
                            {
                            const QString theParamName = pMetaObj->method(i).parameterNames().at(x);
                            const QString theParamType = paramTypes.at(x);
                            paramNames += QString("<code>%1 <SPAN STYLE=\"color: #DD7700\">%2</SPAN></code> <i>%3</i><br>").arg(theParamType).arg(theParamName).arg(help.value(theFuncName + "#" + theParamName, ""));
                            }
                        return paramNames;
                        }
                    }
                }
            }

        }

    return paramNames;
    }

bool RShell::invokeFunction(QString func)
    {
//    LOG_NO_ENTRY;
//    LOG_LEVELEX(9, "function <" + func + ">");

    // QString x = "([a-zA-Z0-9_]*)::([a-zA-Z0-9_]*)\\(([.,<>-+\\[\\]_#'\"������a-zA-Z0-9 ]{0,})\\)";
    // QString x = "(\\w+)::(\\w+)\\(([^)]*)\\)";
    QRegExp exp("([^:]+)::(\\w+)\\(([^)]*)\\)");
    if(exp.indexIn(func) != -1)
        {
        if(exp.numCaptures() != 3)
            return false;

        QString objName = exp.cap(1);
        QString funcName = exp.cap(2);
        QString paramDesc = exp.cap(3);

//        LOG_LEVELEX(1, "objName <%s> | funcName <%s> | params <%s>", qPrintable(objName), qPrintable(funcName), qPrintable(paramDesc));

        if(m_ObjMap.contains(objName))
            {
            QStringList params;
            if(!paramDesc.isEmpty())
                params = paramDesc.split(',');
            QMetaMethod myMethod = findMethod(objName, funcName, params.size());

            QList<QByteArray> paramTypes =  myMethod.parameterTypes();
            if(paramTypes.size() != params.size())
                {
//                LOG_LEVELEX(0, "invalid nr. of params <%d> --> required <%d>", params.size(), paramTypes.size());
                return false;
                }

            QList<const char*> paramList;
            for(int i=0; i<params.size(); i++)
                {
//                LOG_LEVELEX(5, "ParamType: <%s>", paramTypes[i].data());
                const char* pParamData =  createParamObject(paramTypes[i].data(), params[i]);
                if(pParamData)
                    paramList << pParamData;
                }

            return invokeFunction(objName, funcName, paramList);
            }

//        LOG_LEVELEX(0, "no matching object found");
        }
//    else
//        LOG_LEVELEX(0, "regexp failed for <" + func + ">");

    return false;
    }

bool RShell::invokeFunction(QString objName, QString funcName, QList<const char*> params)
    {
//    LOG_NO_ENTRY;

    if(m_ObjMap.contains(objName))
        {
        QMetaMethod myMethod = findMethod(objName, funcName, params.size());
        QList<QByteArray> paramTypes =  myMethod.parameterTypes();
        if(paramTypes.size() != params.size())
            {
//            LOG_LEVELEX(0, "invalid nr. of params <%d> --> required <%d>", params.size(), paramTypes.size());
            return false;
            }

        // create function arguments with value
        QList<QGenericArgument*> argList;
        for(int i=0; i<params.size(); i++)
            {
//            LOG_LEVELEX(5, "ParamType: <%s>", paramTypes[i].data());
            QGenericArgument* pArg = new QGenericArgument(paramTypes.at(i).data(), static_cast<const void *>(params.at(i)));
            argList.append(pArg);
            }

        // create the rest with default QGenericArguments (max. 10)
        for(int i=params.size(); i<10; i++)
            argList.append(new QGenericArgument);

        if(argList.size() != 10)
            return false;

        bool bStatus = false;

//        LOG_LEVELEX(1, "typeName <%s>", myMethod.typeName());

        if(QString(myMethod.typeName()) == "bool")
            {
            QGenericReturnArgument retArg(myMethod.typeName(), &bStatus);

            QMetaObject::invokeMethod(m_ObjMap.value(objName), qPrintable(funcName), Qt::DirectConnection, retArg,
                    *argList.at(0), *argList.at(1), *argList.at(2), *argList.at(3), *argList.at(4),
                    *argList.at(5), *argList.at(6), *argList.at(7), *argList.at(8), *argList.at(9));
            }
        else
            {
            bStatus = QMetaObject::invokeMethod(m_ObjMap.value(objName), qPrintable(funcName), Qt::DirectConnection,
                    *argList.at(0), *argList.at(1), *argList.at(2), *argList.at(3), *argList.at(4),
                    *argList.at(5), *argList.at(6), *argList.at(7), *argList.at(8), *argList.at(9));
            }

        // release memory
        for(int i=0; i<argList.size(); i++)
            delete argList.at(i);

        return bStatus;
        }

    return false;
    }

QMetaMethod RShell::findMethod(QString objName, QString funcName, int nrParams)
    {
//    LOG_NO_ENTRY;
//    LOG_LEVELEX(9, "object <" + objName + "> function <" + funcName + "> nr params <%d>", nrParams);

    QMutexLocker lock(&m_MapLock);

    QObject* pObj = m_ObjMap.value(objName);
    const QMetaObject* pMetaObj = pObj->metaObject();
    QMetaMethod myMethod;
    if(pMetaObj)
        {
//        LOG_LEVELEX(1, "---------------------------------------------------");
//        LOG_LEVELEX(1, "Objectname <%s>", qPrintable(pObj->objectName()));
//        LOG_LEVELEX(1, "Classname <%s>", pMetaObj->className());
//        LOG_LEVELEX(1, "Nr of methods <%d>", pMetaObj->methodCount());
        int nMethodCnt = pMetaObj->methodCount();
        for(int i=0; i<nMethodCnt; i++)
            {
            QMetaMethod method = pMetaObj->method(i);
            QString sig = QString(method.signature());
            sig = sig.left(sig.indexOf('('));
            int inParams = method.parameterTypes().size();

            if (sig == funcName && inParams == nrParams)
                myMethod = method;

//            LOG_LEVELEX(1, "Method: <%s>", method.signature());
            }
//        LOG_LEVELEX(1, "matching method found <%s>", myMethod.signature());
//        LOG_LEVELEX(1, "---------------------------------------------------");
        }

    return myMethod;
    }

const char* RShell::createParamObject(QString type, QString value)
    {
//    LOG_NO_ENTRY;

    bool bOk = false;
    char* pData = NULL;

    // remove any const specifiers from type
    type.replace("const ", "");

    // remove any & specifiers from type
    type.replace("&", "");

    type.replace("uint", "unsigned int");
    type.replace("ushort", "unsigned short");
    type.replace("ulong", "unsigned long");
    type.replace("udouble", "unsigned double");
    type.replace("uchar", "unsigned char");

    if(!m_supportedParamTypes.contains(type))
        {
        if(m_aliasMap.value(type).isEmpty())
            return NULL;
        else
            type = m_aliasMap.value(type);
        }

    if( (type == "int") || (type == "bool") )
        {
        int nValue = value.toInt(&bOk, (value.contains("0x")) ? 0 : 10);
        if(bOk)
            {
            pData = new char[sizeof(int)];
            memset(pData, 0, sizeof(int));
            memcpy(pData, &nValue, sizeof(int));
            }
        }
    else if( (type == "unsigned int"))
        {
        unsigned int nValue = value.toUInt(&bOk, (value.contains("0x")) ? 0 : 10);
        if(bOk)
            {
            pData = new char[sizeof(unsigned int)];
            memset(pData, 0, sizeof(unsigned int));
            memcpy(pData, &nValue, sizeof(unsigned int));
            }
        }
    else if(type == "short")
        {
        short sValue = value.toShort(&bOk, (value.contains("0x")) ? 0 : 10);
        if(bOk)
            {
            pData = new char[sizeof(short)];
            memset(pData, 0, sizeof(short));
            memcpy(pData, &sValue, sizeof(short));
            }
        }
    else if( (type == "unsigned short"))
        {
        unsigned short sValue = value.toUShort(&bOk, (value.contains("0x")) ? 0 : 10);
        if(bOk)
            {
            pData = new char[sizeof(unsigned short)];
            memset(pData, 0, sizeof(unsigned short));
            memcpy(pData, &sValue, sizeof(unsigned short));
            }
        }
    else if(type == "long")
        {
        long lValue = value.toLong(&bOk, (value.contains("0x")) ? 0 : 10);
        if(bOk)
            {
            pData = new char[sizeof(long)];
            memset(pData, 0, sizeof(long));
            memcpy(pData, &lValue, sizeof(long));
            }
        }
    else if( (type == "unsigned long"))
        {
        unsigned long lValue = value.toULong(&bOk, (value.contains("0x")) ? 0 : 10);
        if(bOk)
            {
            pData = new char[sizeof(unsigned long)];
            memset(pData, 0, sizeof(unsigned long));
            memcpy(pData, &lValue, sizeof(unsigned long));
            }
        }
    else if(type == "double")
        {
        double dValue = value.toDouble(&bOk);
        if(bOk)
            {
            pData = new char[sizeof(double)];
            memset(pData, 0, sizeof(double));
            memcpy(pData, &dValue, sizeof(double));
            }
        }
    else if(type == "float")
        {
        float fValue = value.toFloat(&bOk);
        if(bOk)
            {
            pData = new char[sizeof(float)];
            memset(pData, 0, sizeof(float));
            memcpy(pData, &fValue, sizeof(float));
            }
        }
    else if(type == "QString")
        {
        QString* pStr = new QString(value);
        pData = new char[sizeof(QString)];
        memcpy(pData, pStr, sizeof(QString));
        }

    return pData;
    }

void RShell::deleteParamObject(char* pData)
    {
    // FIXME free QString object here --> MEMLEAK
    delete [] pData;
    }

